using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;

namespace Microsoft.Coding4Fun.TweetCraft.Wow
{
    /// <summary>
    /// Acts as the base class for <see cref="IValueChannel"/> implementations.
    /// </summary>
    public abstract class ValueChannelBase : IValueChannel
    {
        private readonly Dictionary<string, object> queuedValues = new Dictionary<string, object>();

        /// <summary>
        /// Queues a value to be sent through the channel.
        /// </summary>
        /// <param name="key">The key.</param>
        /// <param name="value">The value.</param>
        public void QueueValue<T>(string key, T value)
        {
            if (key == null)
                throw new ArgumentNullException("key");

            lock (queuedValues)
            {
                // TODO: Remove queued value when Equals(value, default(T))
                queuedValues[key] = value;
            }
        }

        /// <summary>
        /// Removes a value from the queued values to be sent through the channel.
        /// </summary>
        /// <param name="key">The key.</param>
        public void RemoveValue(string key)
        {
            if (key == null)
                throw new ArgumentNullException("key");

            lock (queuedValues)
            {
                queuedValues.Remove(key);
            }

        }

        /// <summary>
        /// Gets a value indicating whether a value has been queued with the given key.
        /// </summary>
        /// <param name="key">The key.</param>
        /// <returns>
        /// 	<c>true</c>, if a value has been queued; <c>false</c> otherwise.
        /// </returns>
        public bool IsQueued(string key)
        {
            if (key == null)
                throw new ArgumentNullException("key");

            lock (queuedValues)
            {
                return queuedValues.ContainsKey(key);
            }
        }

        /// <summary>
        /// Gets the queued value with the specified key.
        /// </summary>
        /// <typeparam name="T">The type of the value.</typeparam>
        /// <param name="key">The key.</param>
        /// <returns>The queued value.</returns>
        [SuppressMessage("Microsoft.Design", "CA1004", Justification = "The syntax 'var value = valueChannel.GetQueuedValue<int>(...)' is considered better than 'int value = (int)valueChannel.GetQueuedValue(...)'.")]
        public T GetQueuedValue<T>(string key)
        {
            if (key == null)
                throw new ArgumentNullException("key");

            lock (queuedValues)
            {
                return (T)queuedValues[key];
            }
        }

        /// <summary>
        /// Flushes the queued values into the channel.
        /// </summary>
        /// <param name="keepValues">A value indicating whether values should be kept in the channel.</param>
        /// <remarks>
        /// When keepValues is set to true, the values are not cleared and the <see cref="Flushed"/> event is not raised.
        /// </remarks>
        public void Flush(bool keepValues)
        {
            using (TraceScope trace = TraceScope.Information("Flushing channel..."))
            {
                // Lock the queuedValues for the duration of the flush and clearing the values
                lock (queuedValues)
                {
                    // Delegate flushing of the queued values to the concrete class that implements Flush
                    this.Flush(queuedValues);

                    // Only clear queued values if keepValues is false
                    if (!keepValues)
                    {
                        // Clear the queued values
                        queuedValues.Clear();
                    }
                }

                // Signal that the trace scope is complete
                trace.Complete("Channel flushed.");

                // Only raise Flushed event if keepValues is false
                if (!keepValues)
                {
                    // Raise the Flushed event
                    OnFlushed();
                }
            }
        }

        /// <summary>
        /// Flushes the queued values into the channel, clears queued values and raises the <see cref="Flushed"/> event.
        /// </summary>
        public void Flush()
        {
            this.Flush(false);
        }

        /// <summary>
        /// Raised when the queued values are flushed.
        /// </summary>
        public event EventHandler Flushed;

        /// <summary>
        /// Raises the <see cref="Flushed"/> event.
        /// </summary>
        private void OnFlushed()
        {
            if (Flushed != null)
                Flushed(this, EventArgs.Empty);
        }

        /// <summary>
        /// Raised when the channel is updated.
        /// </summary>
        public event EventHandler ChannelUpdated;

        /// <summary>
        /// Raises the <see cref="ChannelUpdated"/> event.
        /// </summary>
        protected void OnChannelUpdated()
        {
            if (ChannelUpdated != null)
                ChannelUpdated(this, EventArgs.Empty);
        }

        /// <summary>
        /// Gets a value indicating whether a value with the given key is available in the channel.
        /// </summary>
        /// <param name="key">The key.</param>
        /// <returns>
        /// 	<c>true</c>, if the value is available; <c>false</c> otherwise.
        /// </returns>
        public abstract bool IsAvailable(string key);

        /// <summary>
        /// Reads a value out of the channel.
        /// </summary>
        /// <typeparam name="T">The type of the value.</typeparam>
        /// <param name="key">The key.</param>
        /// <returns>
        /// The value, if a value with the key was found; <c>null</c> otherwise.
        /// </returns>
        [SuppressMessage("Microsoft.Design", "CA1004", Justification = "The syntax 'var value = valueChannel.ReadValue<int>(...)' is considered better than 'int value = (int)valueChannel.ReadValue(...)'.")]
        public abstract T ReadValue<T>(string key);

        /// <summary>
        /// Keeps an existing value in the channel.
        /// </summary>
        /// <typeparam name="T">The type of the value.</typeparam>
        /// <param name="key">The key.</param>
        [SuppressMessage("Microsoft.Design", "CA1004", Justification = "The syntax 'valueChannel.KeepValue<int>(...)' is considered better than 'valueChannel.KeepValue(typeof(int), ...)'.")]
        public void KeepValue<T>(string key)
        {
            if (key == null)
                throw new ArgumentNullException("key");

            // Lock queuedValues for both operations to keep a single lock for the transaction
            lock (queuedValues)
            {
                // Check whether the value is available
                if (this.IsAvailable(key))
                {
                    // Queue the existing value
                    this.QueueValue(key, this.ReadValue<T>(key));
                }
            }
        }

        /// <summary>
        /// Flushes the specified queued values.
        /// </summary>
        /// <param name="queuedValues">The queued values.</param>
        [SuppressMessage("Microsoft.Design", "CA1006", Justification = "The semantic requirement for the implementation of the Flush abstract method is to be able to iterate through a read-only collection of KeyValuePair<string, object> which translates into the type of the parameter.")]
        protected abstract void Flush(IEnumerable<KeyValuePair<string, object>> queuedValues);
    }
}